Iniciando com FreeRTOS: Como portar e rodar uma demo no Arduino UNO R3

Este post faz parte da série Iniciando com FreeRTOS

Este é o primeiro de 2 artigos voltados para engenheiros de sistemas embarcados que desejam iniciar a elaboração de projetos baseados em RTOS, utilizando especificamente o FreeRTOS¹.

Ao longo deste, mostrarei como começar com uma demo existente e modificá-la para um microcontrolador desejado (target) e, em seguida, a implementação de uma aplicação que utiliza a biblioteca coreMQTT, uma das bibliotecas de conectividade disponíveis na base de código do FreeRTOS.

Esse processo de iniciar com uma demonstração existente e modificá-la para atender necessidades específicas de HW e SW de um determinado projeto é de fato o método recomendado pelo FreeRTOS, como indicado aqui².

Hardware Utilizado

O hardware escolhido para o desenvolvimento deste projeto consiste em um Arduino UNO R3³ e um ESP-01⁴. O Arduino UNO R3 utiliza o microcontrolador ATMega328P da Atmel (atual Microchip), no qual o FreeRTOS será executado. Já o ESP-01, baseado no ESP8266, atuará como módulo de conectividade, para que possamos nos conectar ao servidor (broker) MQTT.

Estes dispositivos foram escolhidos por encontrarem-se disponíveis para mim no momento da elaboração deste artigo, além do que, o projeto FreeRTOS disponibiliza uma demo⁵ para o ATMega323 que utiliza a toolchain WinAVR⁶, baseado no GNU GCC compiler para AVR⁷ o qual utilizaremos aqui. A existência de uma demo oficial utilizando a mesma arquitetura de CPU e compilador que o microcontrolador alvo facilita muito o port para o target, o que demonstro passo a passo a seguir.

Por fim, o Arduino UNO R3 é uma placa comumente encontrada nas culturas “makers” e “DIYs” e de custo acessível. Contudo, o método que demonstro aqui é aplicável não somente ao Arduino UNO R3 como também para outras famílias de microcontroladores. A lista dos dispositivos e toolchains suportados oficialmente pelo projeto FreeRTOS pode ser encontrada aqui⁸.

Clonando o projeto FreeRTOS

Para começar, devemos clonar o projeto FreeRTOS disponível em https://github.com/FreeRTOS/FreeRTOS/. Utilizaremos a tag 202411.00, que disponibiliza o mesmo kernel e bibliotecas da versão 202406-LTS.O projeto FreeRTOS mantém suas bibliotecas em diferentes repositórios no GitHub, e para disponibilizar uma árvore de desenvolvimento completa, a função de git submodules⁹ é utilizada. Para clonarmos corretamente todo o projeto e suas dependências, devemos utilizar o seguinte comando:

$ git clone -b 202411.00 https://github.com/FreeRTOS/FreeRTOS.git --recurse-submodules

Esse processo irá obter todo o código presente em https://github.com/FreeRTOS/FreeRTOS/ como também dos outros repositórios necessários, e portanto, pode levar alguns minutos para ser concluído.

Crie um diretório vazio e dentro dele rode o comando acima para clonar a base de código do projeto FreeRTOS. No meu caso, utilizarei o diretório ~/workspace/aprendizado. Após terminado, teremos um diretório chamado FreeRTOS com o seguinte conteúdo:

Compilando a demo existente

A demonstração que utilizaremos como base reside em FreeRTOS/FreeRTOS/Demo/AVR_ATMega323_WinAVR/. Dentro deste diretório existe o código-fonte da aplicação main.c, o header de configuração necessário a todas as aplicações que utilizam FreeRTOS FreeRTOSConfig.h, o código-fonte de outras partes do sistemas necessários ao funcionamento da aplicação, e um makefile responsável por compilar o código-fonte e gerar a imagem final para ser gravada no target.

Note que o código da aplicação é relativamente simples e uma pessoa familiarizada com C não deverá ter dificuldades em compreendê-lo. Essa é mais uma vantagem ao se utilizar o FreeRTOS, desenvolver aplicações utilizando o kernel do FreeRTOS é extremamente simples e direto.

O próximo passo é voltar ao diretório de trabalho e instalar a toolchain disponibilizada em (7).

Baixe a toolchain AVR 8-Bit compatível com seu sistema operacional. No meu caso, utilizo a toolchain AVR 8-Bit para Linux. Após descomprimir o pacote no meu diretório de trabalho, renomeio o diretório da toolchain de avr8-gnu-toolchain-linux_x86_64 para avr-toolchain, de modo a facilitar futuras referências a esse caminho.

Verifique que em avr-toolchain/bin você possui as ferramentas necessárias a compilação cruzada de código para arquitetura AVR:

Adicione este diretório em sua variável $PATH executando o comando a seguir (caso necessário, adapte o comando o seu ambiente):

export PATH=~/workspace/aprendizado/avr-toolchain/bin:$PATH

Aqui estamos prontos para compilar a aplicação de demonstração para o ATMega323.

Retorne ao diretório da demo –FreeRTOS/FreeRTOS/Demo/AVR_ATMega323_WinAVR – e entre com o comando make (ou make all) e a compilação deverá concluir sem erros:

Caso você encontre algum erro, reveja os passos anteriores, e confirme que os caminhos e toolchain estão de acordo com o seu ambiente de desenvolvimento.

Se tudo ocorreu bem, você deve visualizar a mensagem Errors: none e também os arquivos rtosdemo.xxx conforme ilustrado acima.

O arquivo rtosdemo.hex corresponde a imagem que pode ser gravada no MCU ATMega323 utilizando um programador compatível. Contudo, nosso objetivo é gerar a mesma aplicação de demonstração, mas para o ATMega328P, o MCU presente no HW do Arduino UNO R3.

Execute make clean para limpar os arquivos gerados pelo processo de compilação.

Portando a demo para o ATMega328P

No final desta seção teremos as modificações necessárias para a aplicação de demonstração rodar em um ATMega328P. Mas primeiro precisamos discutir com mais detalhes cada um dos arquivos do código-fonte para determinar quais modificações são necessárias.

Não faça as alterações manualmente no código-fonte da aplicação enquanto lê o artigo. No início da sessão seguinte, eu disponibilizo um repositório contendo todas as mudanças necessárias.

FreeRTOSConfig.h

Todo projeto que utiliza o FreeRTOS deve definir um header de configuração chamado FreeRTOSConfig.h. Na imagem abaixo, vemos as configurações para demo do ATMega323:

De cara, vemos que a primeira alteração que devemos fazer refere-se à configuração do clock de CPU. Para o ATMega323, o valor corresponde a 8MHz, e no hardware do Arduino UNO R3, o valor do clock utilizado é de 16MHz. Fora isso, vamos manter todos os outros valores como estão. A definição de cada parâmetro pode ser encontrada aqui¹⁰.

main.c

Em main.c está definida a aplicação principal, a qual encontra-se detalhadamente comentada. Em resumo, a aplicação consiste em inicializar o HW com a chamada a vParTestInitialise():

registrar (ou “criar”) algumas tarefas para serem executadas:

E iniciar o scheduler do FreeRTOS:

O código-fonte da maioria das tarefas executadas nessa aplicação pode ser encontrado em FreeRTOS/FreeRTOS/Demo/Common/Minimal e os respectivos headers em FreeRTOS/FreeRTOS/Demo/Common/include.

Nosso objetivo aqui não é discutir em detalhes o funcionamento das tarefas e do escalonador no FreeRTOS, mas sim focar no port de um MCU para o outro. Ainda assim, é importante ter um entendimento de alto nível de como as tarefas funcionam. Em resumo, elas são definidas tendo uma função como ponto de entrada e uma prioridade de execução e, a princípio, elas não retornam, ou seja, elas devem executar em loop infinito. O escalonador é responsável pela execução e troca de contexto entre as diferentes tarefas de acordo com a prioridade com a qual cada tarefa foi registrada. Uma vez chamado vTaskStartScheduler(), o escalonador inicia a execução das tarefas e essa função não retorna.

Para saber mais sobre tarefas, prioridades e outros recursos importantes ao FreeRTOS, recomendo a leitura deste artigo¹¹ do Pedro Bertoleti além da documentação¹² oficial do projeto FreeRTOS.

Em alto nível, essa aplicação de exemplo executa algumas tarefas que utilizam recursos de CPU e memória (vStartIntegerMathTasks, vStartPolledQueueTasks, vStartRegTestTasks) e hardware (vAltStartComTestTasks), e caso nenhum erro seja detectado, uma porta de saída digital tem o seu nível lógico alterado:

Caso um LED for conectado a esta porta digital, ele proverá um feedback visual de que a aplicação está executando sem erros. Por fim, co-rotinas para piscar 3 LEDs também são criadas e, caso LEDs sejam ligados a determinadas portas digitais, o usuário também terá feedback visual sobre a execução das co-rotinas de LED.

É importante notar que de modo geral, o código de uma aplicação é completamente independente do HW em que a mesma está sendo executada, e por isso não precisamos fazer nenhuma alteração em main.c

makefile

Como estamos usando uma toolchain que é compatível entre ATMega323 e ATMega328P, a única mudança que precisamos fazer no makefile é na linha 34, na variável MCU:

diff --git a/makefile b/makefile
index 875aa78..3feb547 100644
--- a/makefile
+++ b/makefile
@@ -31,7 +31,7 @@
 # MCU name
-MCU = atmega323
+MCU = atmega328p^M
 # Output format. (can be srec, ihex, binary)
 FORMAT = ihex

E podemos manter o restante como está.

Drivers de IO e Comunicação Serial

Os arquivos PartTest/ParTest.c e serial/serial.c implementam respectivamente um driver de output digital e um driver de comunicação serial utilizando a UART do MCU. Alterações no código fonte desses arquivos são necessárias visando o HW em que vamos executar a aplicação. Essas alterações são importantes para que a aplicação de demonstração funcione corretamente, uma vez que algumas tarefas utilizadas necessitam das interfaces implementadas em ParTest.c e serial.c. Além disso, também faremos uso desses drivers na nossa aplicação principal.

Nesse ponto, é muito importante a consulta dos datasheets dos microcontroladores para que se possa entender quais portas, registradores e interrupções serão precisos configurar. Outro material de consulta importante são os headers de IO do MCU presentes na toolchain. Eles podem ser encontrados em avr-toolchain/avr/include/avr. Para o ATMega328P, o arquivo de interesse é iom328p.h. Ele contém as macros utilizadas nas configurações de IO, registradores e interrupções. Não irei detalhar as mudanças necessárias para a implementação da aplicação no Arduino UNO R3, mas elas estarão disponibilizadas no repositório compartilhado na próxima sessão. O leitor interessado pode verificar as alterações que tiveram que ser feitas.

port.c

Até aqui verificamos apenas o código fonte da aplicação, disponível em FreeRTOS/FreeRTOS/Demo/AVR_ATMega323_WinAVR. Em nenhum momento averiguamos o código do kernel do FreeRTOS em si. O kernel FreeRTOS é implementado em 3 arquivos fontes, tasks.c, list.c e queue.c, localizados em FreeRTOS/FreeRTOS/Source, e utilizados por qualquer projeto. Dependendo dos recursos empregados em uma aplicação, outros arquivos fontes como timers.c ou croutine.c podem ser necessários¹³.

O código do kernel é implementado de modo a ser independente de qualquer solução de HW, MCU e toolchain que possa ser utilizado, mas para ele funcionar, é necessária a implementação de uma camada de abstração que é dependente da solução utilizada. O código dependente encontra-se em FreeRTOS/Source/portable/[compiler]/[architecture]. No caso do ATMega323, esse caminho corresponde a FreeRTOS/Source/portable/GCC/ATMega323. No nosso port para o ATMega328P, eu suponho que a camada de abstração existente para o ATMega323 será compatível com o ATMega328P, salvo pequenas alterações que demonstro a seguir.

De modo geral, o código do port é responsável por inicializar o stack do kernel, salvar e restaurar os estados dos registradores relevantes e stack de cada tarefa para a troca de contexto entre elas, e gerar o “tick”. O “tick” é uma interrupção gerada com período constante. Ele é utilizado no FreeRTOS para medir intervalos de tempo e pelo escalonador para realizar as trocas de tarefas.

No caso do ATMega323, é utilizado o Timer/Contador 1 do MCU para geração do tick. Para o ATMega328P, também utilizaremos o Timer/Contador 1 para gerar o tick, mas precisamos adaptar o código em port.c para utilizar os registradores e bitmaks compatíveis com o ATMega328P. Novamente consultar o datasheet de ambos os microcontroladores se faz necessário, e as seguintes alterações devem ser implementadas:

diff --git a/portable/GCC/ATMega323/port.c b/portable/GCC/ATMega323/port.c
index 98f0d56..26ea4f3 100644
--- a/portable/GCC/ATMega323/port.c
+++ b/portable/GCC/ATMega323/port.c
@@ -52,7 +52,7 @@
 #define portCLEAR_COUNTER_ON_MATCH              ( ( uint8_t ) 0x08 )
 #define portPRESCALE_64                         ( ( uint8_t ) 0x03 )
 #define portCLOCK_PRESCALER                     ( ( uint32_t ) 64 )
-#define portCOMPARE_MATCH_A_INTERRUPT_ENABLE    ( ( uint8_t ) 0x10 )
+#define portCOMPARE_MATCH_A_INTERRUPT_ENABLE    ( ( uint8_t ) 0x02 )
 /*-----------------------------------------------------------*/
@@ -394,9 +394,9 @@ static void prvSetupTimerInterrupt( void )
     /* Enable the interrupt - this is okay as interrupt are currently globally
      * disabled. */
-    ucLowByte = TIMSK;
+    ucLowByte = TIMSK1;
     ucLowByte |= portCOMPARE_MATCH_A_INTERRUPT_ENABLE;
-    TIMSK = ucLowByte;
+    TIMSK1 = ucLowByte;
 }
 /*-----------------------------------------------------------*/

Com isso, estamos finalmente aptos a gerar a aplicação de demonstração para o ATMega328P.

Compilando e Rodando a Demo no ATMega328P

Primeiro vamos clonar o código com o port para o ATMega328P.

Em FreeRTOS/FreeRTOS rode o seguinte comando:

$ git clone -b portFrom_atmega323 https://github.com/vinRocha/ATMega328P_FreeRTOS.git AVR_ATMega328P_GCC

Após isso, você deverá ter o diretório FreeRTOS/FreeRTOS/AVR_ATMega328P_GCC com o seguinte conteúdo:

O conteúdo do diretório se assemelha com o que observamos em FreeRTOS/FreeRTOS/Demo/AVR_ATMega323_WinAVR, com exceção do diretório portable, que contém as alterações necessárias em port.c para utilização do ATMega328P. Idealmente, esse código residiria em FreeRTOS/Source/portable/GCC/ATMega328P, mas nesse exemplo, deixo tudo junto com o código da aplicação para facilitar o acompanhamento do leitor.

Aqui, como na seção 3, o leitor deve ter configurado a variável$PATH com caminho da toolchain instalada em seu ambiente. Após isso, basta rodar make ou make all para gerar a imagem final rtosdemo.hex.

Para gravarmos a imagem no MCU, utilizaremos o gravador disponível com o Arduino IDE.

Conecte o Arduino UNO R3 em seu computador e abra o Arduino IDE

Navegue em File -> Preferences e confirme que Show verbose output during upload encontra-se selecionado:

Agora faça o upload do projeto vazio (ou qualquer outro projeto) para que na seção ‘Output’ possamos ver o comando necessário para gravar uma imagem no Arduino UNO R3. Corra as mensagens para cima até que você encontre o início do comando do avrdude:

no meu caso, o comando utilizado foi:

"/home/vsilva1/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/bin/avrdude" "-C/home/vsilva1/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/etc/avrdude.conf" -v -V -patmega328p -carduino "-P/dev/ttyACM0" -b115200 -D "-Uflash:w:/home/vsilva1/.cache/arduino/sketches/8AC1A70F1160CAB1BDCB29CB1439B1BF/empty.ino.ino.hex:i"

Agora basta substituirmos no final do comando acima o nome da imagem a ser gravada, o que para mim resulta em:

"/home/vsilva1/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/bin/avrdude" "-C/home/vsilva1/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/etc/avrdude.conf" -v -V -patmega328p -carduino "-P/dev/ttyACM0" -b115200 -D "-Uflash:w: rtosdemo.hex :i"

Após rodar esse comando dentro do diretório FreeRTOS/FreeRTOS/AVR_ATMega328P_GCC, temos o Arduino UNO R3 gravado com a imagem do FreeRTOS que acabamos de compilar:

A tarefa ComTestTasks da aplicação de demonstração precisa que o pino serial TX do dispositivo seja conectado ao RX, de modo que a tarefa possa “ler” de volta os valores que estão sendo transmitidos. Contudo, não deixe o TX conectado ao RX durante a gravação da imagem, pois ela irá falhar. Deixe o TX flutuando, e somente após a gravação ser concluída com sucesso, conecte o TX ao RX e então reset o MCU utilizando o push button, conforme ilustrado acima.

O LED de status das tarefas, ou seja, que indica que as tarefas continuam a executar sem erro é o LED built-in da placa Arduino UNO R3, indicado pela letra L no PCB. Se tudo estiver bem, esse LED deverá alterar de estado a cada 3 segundos (customizável em main.c). As co-rotinas de LED utilizam as portas digitais 2, 3, e 4 do Arduino UNO R3. Caso você conecte LEDs a essas portas, você terá feedback visual de que as co-rotinas estão em execução.

Para maiores detalhes sobre a aplicação de demonstração, verifique o código-fonte de cada tarefa.

Conclusão

Vimos nesse artigo o passo a passo de como começar com o código-fonte do FreeRTOS e realizar as modificações necessárias para implementação em um microcontrolador, dado que esse microcontrolador seja da mesma arquitetura de um MCU já suportado. Ao longo do port, nos familiarizamos com a organização do código-fonte e com a filosofia de funcionamento do FreeRTOS, e por fim geramos a aplicação de demonstração compatível com o HW que tínhamos em mãos.

Esse primeiro artigo realiza ao leitor uma pequena introdução ao mundo do FreeRTOS e fornece as referências necessárias para que o mesmo aprofunde seu conhecimento. Contudo, realizar esse estudo de posse de um hardware onde se possa colocar em prática os conhecimentos adquiridos é fundamental para a real absorção dos mesmos.

Observe que esse trabalho não visa prover um port do FreeRTOS para o ATMega328P. O ATMega328P apenas era o MCU disponível para mim no momento de escrita deste artigo. De fato, já existe um port do FreeRTOS para o ATMega328P e outros MCUs da família AVR-ATMega. Esse port encontra-se disponível em https://github.com/feilipu/Arduino_FreeRTOS_Library ¹⁴ e nas bibliotecas do Arduino IDE. O port também pode ser encontrado em FreeRTOS/FreeRTOS/Source/portable/ThirdParty/GCC/ATmega, o que eu recomendo fortemente que seja utilizado ao invés do port fornecido nesse artigo, pois o port do Phillip Stevens é melhor testado e suportado.

No artigo a seguir, veremos como utilizar a biblioteca coreMQTT disponível no código-fonte do FreeRTOS para criar uma aplicação de IoT.

Referências

  1. https://freertos.org
  2. https://freertos.org/Documentation/01-FreeRTOS-quick-start/01-Beginners-guide/02-Quick-start-guide
  3. https://docs.arduino.cc/hardware/uno-rev3/
  4. https://docs.ai-thinker.com/en/esp8266
  5. https://freertos.org/Documentation/02-Kernel/03-Supported-devices/04-Demos/Atmel-now-Microchip/Atmel_AVR_Mega_AVR
  6. https://winavr.sourceforge.net/
  7. https://www.microchip.com/en-us/tools-resources/develop/microchip-studio/gcc-compilers
  8. https://freertos.org/Documentation/02-Kernel/03-Supported-devices/00-Supported-devices
  9. https://git-scm.com/book/en/v2/Git-Tools-Submodules
  10. https://www.freertos.org/Documentation/02-Kernel/03-Supported-devices/02-Customization
  11. https://embarcados.com.br/rtos-para-iniciantes-com-arduino-e-freertos/
  12. https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/01-Tasks-and-co-routines/00-Tasks-and-co-routines
  13. https://www.freertos.org/Documentation/02-Kernel/06-Coding-guidelines/01-Source-code-organization
  14. https://github.com/feilipu/Arduino_FreeRTOS_Library

Iniciando com FreeRTOS

Desenvolvendo uma Aplicação IoT com FreeRTOS e coreMQTT com Arduino e ESP01
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Software » Sistemas Operacionais » Iniciando com FreeRTOS: Como portar e rodar uma demo no Arduino UNO R3

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: